/*
 * Javlov - a Java toolkit for reinforcement learning with multi-agent support.
 * 
 * Copyright (c) 2009 Matthijs Snel
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.javlov.world;

import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.List;

import net.javlov.VectorState;

/**
 * Sensor that manages a group of child sensors. The child sensors need to implement the
 * GroupedSensor interface in order to be able to produce a reading from a list of predefined
 * objects. The group of objects that the children read from is prefiltered from the world by the
 * CompositeSensor. In addition the sensor only modifies the position and rotation of its children
 * when necessary, i.e. when there are objects in range and the children need to produce a reading.
 * 
 * NOTE: this class doesn't produce a reading if there are less than 2 bodies within range; this
 * is because DistanceSensors always detect the agent's own body. And this class has only been tested
 * with DistanceSensors.
 * 
 * @author Matthijs Snel
 *
 */
public class CompositeSensor implements PhysicalSensor {

	private int numChildren;
	protected List<GroupedSensor> children;
	protected World world;
	protected Path2D.Double rangeArea;
	
	private double totaldx, totaldy, totalRot, lastCentreX, lastCentreY;
	
	public CompositeSensor() {
		children = new ArrayList<GroupedSensor>();
		rangeArea = new Path2D.Double();
	}
	
	public CompositeSensor(World world) {
		this();
		setWorld(world);
	}
	
	public void add(GroupedSensor s) {
		children.add(s);
		rangeArea.append(s.getRangeArea(), false);
	}

	public void setWorld(World w) {
		world = w;
	}
	
	@Override
	public void rotate(double angle) {
		for ( GroupedSensor s : children )
			s.rotate(angle);
		reCalcRangeArea();
	}

	@Override
	public void rotate(double angle, double x, double y) {
		totalRot += angle;
		lastCentreX = x;
		lastCentreY = y;
		rangeArea.transform(AffineTransform.getRotateInstance(angle, x, y));	
	}

	@Override
	public void setBody(Body b) {
		//not needed
	}
	
	@Override
	public void translate(double dx, double dy) {
		totaldx += dx;
		totaldy += dy;
		rangeArea.transform(AffineTransform.getTranslateInstance(dx, dy));	
	}

	@Override
	public double[] getReading() {
		List<Body> objectsInRange = world.getIntersectingObjects(rangeArea.getBounds2D());
		//TODO dangerous assumption: own body is always in range for distance sensors,
		//but might not be for other sensors
		if ( objectsInRange.size() < 2 ) 
			return new double[numChildren];

		VectorState st = new VectorState();
		for ( GroupedSensor gs : children ) {
			gs.translate(totaldx, totaldy);
			gs.rotate(totalRot, lastCentreX, lastCentreY);
			st.append(gs.getReadingFromObjects(objectsInRange));
		}
		totalRot = totaldx = totaldy = 0;
		return st.getData();
	}

	@Override
	public void init() {
		for ( GroupedSensor s : children )
			s.init();
		//assume no more sensors added
		numChildren = children.size();
		
	}

	@Override
	public void reset() {
		for ( GroupedSensor s : children )
			s.reset();
		numChildren = children.size();
	}
	
	private void reCalcRangeArea() {
		rangeArea.reset();
		for ( GroupedSensor s : children )
			rangeArea.append(s.getRangeArea(), false);
	}

	@Override
	public Shape getRangeArea() {
		return rangeArea;
	}

	@Override
	public int getReadingDim() {
		int dim = 0;
		for ( GroupedSensor gs : children )
			dim += gs.getReadingDim();
		return dim;
	}
}
